package controller

import (
	"strconv"
	"struggle/dao/mysql"
	"struggle/logic"
	"struggle/model"
	"struggle/pkg/snowflake"

	"github.com/gin-gonic/gin"
	"github.com/spf13/cast"
	"go.uber.org/zap"
)

type ArticleController struct{}

var DefaultArticleController = &ArticleController{}

// RegisterRoute 管理 ImageController 路由
func (a ArticleController) RegisterRoute(r *gin.Engine) {
	// 文章 路由组
	article := r.Group("/article")
	{
		// 文字列表
		article.GET("/", a.list)
		// 文章详情
		article.GET("/p", a.detail)
		// 需要登录后才能访问的放在下面
		article.Use(JWTAuthMiddleware())
		// 创建文章
		article.POST("/add", a.publish)
		// 编辑文章
		article.GET("/edit", a.editGet)
		article.POST("/edit", a.edit)
	}
}

// 参数校验 --> 校验必要的数据是否存在
// 防止 XSS 攻击 --> `html/template` 内容检测和转义
// 敏感词过滤 --> 涉黄、涉恐等屏蔽词
// 数据库创建记录 --> dao 层创建记录
func (a ArticleController) publish(c *gin.Context) {
	// 1. 通过 Bind 和 Validate 进行参数绑定和校验
	article := &model.Article{}
	if err := c.ShouldBindJSON(&article); err != nil {
		// 返回参数错误的响应
		zap.L().Error("invalid params", zap.Error(err))
		ResponseErrorWithMsg(c, CodeInvalidParams, err.Error())
		return
	}
	// 2. 因为发布文章一定要登录，获取当前登录的用户信息
	userSn, err := getCurrentUserSn(c)
	if err != nil {
		zap.L().Error("用户发布文章时未登录")
		ResponseError(c, CodeNotLogin)
		return
	}
	article.UID = userSn
	// 3. Twitter SnowFlake 算法生成文章的 SN
	sn, err := snowflake.GenID()
	article.Sn = sn
	if err != nil {
		zap.L().Error("generate article id failed", zap.Error(err))
		return
	}
	// 4. 发布文章逻辑处理
	if err = logic.DefaultArticle.Publish(article); err != nil {
		zap.L().Error("create article failed", zap.Error(err))
		ResponseError(c, CodeServerBusy)
		return
	}
	zap.L().Debug("create article success", zap.Any("article", article))
	// 4. 发布成功后，返回的 JSON 数据中包含 redirect, 用于跳转到用户主页
	ResponseSuccess(c, article.Sn)
}

// 文章详情
func (a ArticleController) detail(c *gin.Context) {
	articleSn, err := getIDFromQuery(c, "article_id")
	if err != nil {
		zap.L().Error("invalid query param", zap.Error(err))
		ResponseError(c, CodeInvalidParams)
		return
	}

	// 给指定的文章阅读数+1
	ip := c.ClientIP()
	err = logic.DefaultArticle.IncReadCount(articleSn, ip, 0)
	if err != nil {
		zap.L().Error("IncReadCount failed", zap.Error(err))
		return
	}

	// Markdown 是否由客户端解析
	clientParse := c.Query("client_parse")
	client := cast.ToBool(clientParse)

	// 获取文章详情和文章扩展数据
	article, catalog, statistics, err := logic.DefaultArticle.FindOne(articleSn, client)
	if err != nil {
		zap.L().Error("get article detail failed", zap.Error(err))
		return
	}
	//zap.L().Debug("article", zap.Any("article", article))

	// 构建返回 json 数据
	data := map[string]interface{}{
		"article":    article,
		"catalog":    catalog,
		"statistics": statistics,
	}

	// 获取文章作者信息
	user, err := mysql.GetUserByID(strconv.Itoa(int(article.UID)))
	if err != nil {
		zap.L().Error("get user info failed", zap.Error(err))
		return
	}
	data["user"] = user.Email

	// 获取文章作者用户扩展数据
	data["user_count"], _ = logic.DefaultUser.FindUserCount(article.UID)
	//zap.L().Debug("article", zap.Any("data", data))
	ResponseSuccess(c, data)
}

func (a ArticleController) editGet(c *gin.Context) {
	sn, err := getIDFromQuery(c, "article_id")
	if err != nil {
		ResponseError(c, CodeInvalidParams)
		return
	}
	article, err := mysql.DefaultArticle.FindArticle(sn)
	if err != nil {
		ResponseError(c, CodeServerBusy)
		return
	}
	userSn, err := getCurrentUserSn(c)
	if err != nil {
		zap.L().Error("用户编辑文章时未登录")
		ResponseError(c, CodeNotLogin)
		return
	}
	if article.UID != userSn {
		ResponseErrorWithMsg(c, CodeInvalidAuthFormat, "不是本篇文章作者")
		return
	}
	ResponseSuccess(c, article)
	return
}

// 编辑文章
func (a ArticleController) edit(c *gin.Context) {
	sn, err := getIDFromQuery(c, "article_id")
	if err != nil {
		ResponseError(c, CodeInvalidParams)
		return
	}
	article := &model.Article{Sn: sn}
	if err := c.ShouldBindJSON(&article); err != nil {
		ResponseError(c, CodeInvalidParams)
		return
	}
	if len(article.Title) == 0 || len(article.Content) == 0 {
		ResponseError(c, CodeInvalidParams)
		return
	}

	userSn, err := getCurrentUserSn(c)
	if err != nil {
		zap.L().Error("用户编辑文章时未登录")
		ResponseError(c, CodeNotLogin)
		return
	}
	article.UID = userSn
	err = logic.DefaultArticle.Update(article)
	if err != nil {
		ResponseError(c, CodeServerBusy)
		return
	}
	// 编辑成功后重定向到文章详情页
	ResponseSuccess(c, article)
	return
}

// 文章列表
func (a ArticleController) list(c *gin.Context) {
	offset, limit := getOffsetAndLimit(c)
	data, err := logic.DefaultArticle.Find(offset, limit)
	if err != nil {
		zap.L().Error("logic.DefaultArticle.Find failed", zap.Error(err))
		ResponseError(c, CodeServerBusy)
		return
	}
	zap.L().Debug("get article list success", zap.Any("data", data))
	ResponseSuccess(c, data)
}
